﻿using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using System.Web;
using FluentNHibernate.Cfg;
using FluentNHibernate.Conventions;
using NHibernate;
using NHibernate.Cfg;

using System.Linq;
using log4net;

namespace LibYanu.DAO
{
   

    public class Sc<T> : IScope
        where T : IPersistenceModelGenerator, new()
    {
        private static string nhibernateSession = "nhibernate.session";
        public static void SetDiff(string marker)
        {
            if (marker != null)
            {
                System.Threading.Interlocked.CompareExchange<string>(ref nhibernateSession,
                    marker, "nhibernate.session");
            }

        }

        private static object synchObj = new object();
        private bool isOwner = false;

        internal static ISessionFactory sessionFactory;
        protected static ISessionFactory SessionFactory
        {
            get { return sessionFactory; }
        }

        internal static void SetSessionFactory(ISessionFactory factory)
        {
            System.Threading.Interlocked.CompareExchange<ISessionFactory>(ref sessionFactory, factory, null);
        }

        private static Configuration cfg;
        public static Configuration Configuration
        {
            get { return cfg; }
            internal set
            {
                System.Threading.Interlocked.CompareExchange<Configuration>(ref cfg, value, null);
            }
        }

        public static string GeneratorTypeName
        {
            get;
            set;
        }

        public static void MappingInitialization(MappingConfiguration mc)
        {
            IPersistenceModelGenerator instance = new T();

            mc.FluentMappings.AddFromAssemblyOf<T>();

            var model = instance.Generate();
            foreach (var hbm in instance.HbmMaps)
                mc.HbmMappings.AddFromAssemblyOf<T>();
            foreach (var cls in instance.ClassMaps)
                mc.FluentMappings.Add((Type)cls);
            if (instance.HasAuto)
            {
                var lstConv = model.Conventions.Find<IConvention>();

                foreach (var conv in lstConv)
                    mc.FluentMappings.Conventions.Add(conv);

                mc.AutoMappings.Add(model);
            }

            if (!string.IsNullOrEmpty(ExportedDirectory))
            {
                mc.FluentMappings.ExportTo(ExportedDirectory);
                mc.AutoMappings.ExportTo(ExportedDirectory);
            }
        }

        public static string ExportedDirectory
        {
            get;
            set;
        }

        private FlushMode _flushMode;
        private SessionBehaviour _behaviour;

        public Sc()
            : this(FlushMode.Commit, SessionBehaviour.JoinPrevious)
        {
        }
        private EventHandler cleanUpEvent;
        public Sc(FlushMode flushMode, SessionBehaviour behaviour)
        {
            HttpContext ctx = HttpContext.Current;
            if (ctx != null)
            {
                lock (ctx)
                    if (ctx != null)
                    {
                        if (ctx.Items[nhibernateSession] == null)
                        {
                            ctx.Items[nhibernateSession] = new List<ISession>();
                            isOwner = true;
                        }
                        CleanUpRegistrar registrar = CleanUpRegistrar.Instance;
                        if (cleanUpEvent == null)
                            cleanUpEvent = new EventHandler(ApplicationInstance_EndRequest);
                        registrar.CleanUp += cleanUpEvent;
                    }
            }
            else
                if (CallContext.GetData(nhibernateSession) == null)
                    lock (synchObj)
                        if (CallContext.GetData(nhibernateSession) == null)
                        {
                            CallContext.SetData(nhibernateSession, new List<ISession>());
                            isOwner = true;
                        }

            _flushMode = flushMode;
            _behaviour = behaviour;
        }

        private ISession scopeSession;
        private ISession InitiateSessionBasedOn(FlushMode flushMode, SessionBehaviour behaviour, IList<ISession> sessionList)
        {
            ISession session = null;
            if (behaviour == SessionBehaviour.JoinPrevious)
            {
                if (global::NHibernate.Context.CurrentSessionContext.HasBind(SessionFactory))
                {
                    session = SessionFactory.GetCurrentSession();
                    //if (!sessionList.Contains(session))
                    //    sessionList.Add(session);

                    if (session.FlushMode != flushMode)
                    {
                        ISession foundSession = null;
                        int sIdx = sessionList.Count;
                        while (sIdx-- > 0)
                        {
                            var ss = sessionList[sIdx];
                            if (ss.FlushMode == flushMode)
                            {
                                foundSession = ss;
                                break;
                            }
                        }
                        if (foundSession == null)
                        {
                            foundSession = OpenSession(flushMode);
                            sessionList.Add(foundSession);
                        }
                        session = foundSession;
                    }
                }
                else
                {
                    session = OpenSession(flushMode);
                    sessionList.Add(session);
                    global::NHibernate.Context.CurrentSessionContext.Bind(session);
                }
            }
            else
            {
                if (!global::NHibernate.Context.CurrentSessionContext.HasBind(SessionFactory))
                {
                    session = OpenSession(flushMode);
                    global::NHibernate.Context.CurrentSessionContext.Bind(session);
                }
                else
                    session = OpenSession(flushMode);
                sessionList.Add(session);
            }
            return session;
        }

        private ISession OpenSession(FlushMode flushMode)
        {
            var session = sessionFactory.OpenSession();
            session.FlushMode = flushMode;

            var dtx = (global::NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.DistributedTransactionContext)session.GetSessionImplementation().TransactionContext;
            if (dtx != null)
                dtx.ShouldCloseSessionOnDistributedTransactionCompleted = true;
            return session;
        }

        private static void CloseAndDisposeSessionAndTransaction(ISession session)
        {
            var tx = session.Transaction;
            var si = session.GetSessionImplementation();
            try
            {
                if (tx != null)
                    try
                    {
                        if (session.FlushMode == FlushMode.Commit && tx.IsActive)
                            try
                            {
                                if (!tx.WasCommitted && !tx.WasRolledBack)
                                    tx.Commit();
                            }
                            catch
                            {
                                try
                                {
                                    tx.Rollback();
                                }
                                catch
                                {
                                }
                            }
                    }
                    finally
                    {
                        tx.Dispose();
                    }
                var dtx = (global::NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.DistributedTransactionContext)si.TransactionContext;
                if (dtx == null)
                    session.Close();
            }
            finally
            {
                session.Dispose();
            }
        }

        private static IList<ISession> GetSessionList()
        {
            IList<ISession> sessionList = null;
            HttpContext ctx = HttpContext.Current;
            if (ctx != null)
                sessionList = (IList<ISession>)ctx.Items[nhibernateSession];
            else
                sessionList = (IList<ISession>)CallContext.GetData(nhibernateSession);
            return sessionList;
        }

        private static void CloseAndDisposeSessionAndTransaction()
        {
            var sessionList = GetSessionList();
            if (sessionList != null)
            {
                //var ctxSession = global::NHibernate.Context.CurrentSessionContext.Unbind(SessionFactory);
                var ctxSession = SessionFactory.GetCurrentSession();
                if (sessionList.Contains(ctxSession))
                    global::NHibernate.Context.CurrentSessionContext.Unbind(SessionFactory);
                foreach (var session in sessionList)
                    CloseAndDisposeSessionAndTransaction(session);
                sessionList.Clear();
            }
        }

        void ApplicationInstance_EndRequest(object sender, EventArgs e)
        {
            if (cleanUpEvent != null)
                CleanUpRegistrar.Instance.CleanUp -= cleanUpEvent;
            HttpContext context = HttpContext.Current;
            lock (context)
            {
                CloseAndDisposeSessionAndTransaction();
                context.Items.Remove(nhibernateSession);
            }
        }

        public ISession Session
        {
            get
            {
                //HttpContext ctx = HttpContext.Current;
                //if (ctx != null)
                //    if (scopeSession == null)
                //        lock (ctx)
                //            if (scopeSession == null)
                //                scopeSession = InitiateSessionBasedOn(_flushMode, _behaviour, (IList<ISession>)ctx.Items[nhibernateSession]);
                //else
                object synch;
                HttpContext ctx = HttpContext.Current;
                if (ctx != null)
                    synch = ctx;
                else
                    synch = synchObj;
                if (scopeSession == null)
                    lock (synch)
                        if (scopeSession == null)
                            scopeSession = InitiateSessionBasedOn(_flushMode, _behaviour, GetSessionList());
                return scopeSession;

            }
        }

        #region IDisposable Members

        private void Dispose(bool disposing)
        {
            if (disposing)
                GC.SuppressFinalize(this);

            if (this.isOwner &&
                HttpContext.Current == null &&
                GetSessionList() != null)
                lock (synchObj)
                    if (GetSessionList() != null)
                    {
                        CloseAndDisposeSessionAndTransaction();
                        CallContext.FreeNamedDataSlot(nhibernateSession);
                    }
        }

        public void Dispose()
        {
            Dispose(true);
        }

        ~Sc()
        {
            Dispose(false);
        }

        #endregion
    }


    public interface IScope : IDisposable
    {
        ISession Session { get; }
    }

    public enum SessionBehaviour : short
    {
        JoinPrevious = 0,
        CreateNew = 1
    }

}
